package com.geeksss.excel.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelAnalysisStopException;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.read.listener.ReadListener;
import com.geeksss.excel.constant.ExcelConstant;
import com.geeksss.excel.model.ReadErrorModel;
import com.geeksss.excel.model.UserInfoModel;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * 用户信息读取监听器，用于在读取Excel时对用户信息进行处理。
 * 实现了读取监听器接口，特定于处理UserInfoModel类型的对象。
 */
@Slf4j
public class UserInfoReadListener implements ReadListener<UserInfoModel> {
    
    /**
     * 批量处理的大小。
     */
    private final int batchSize;
    /**
     * 用于临时存储满足条件的用户信息的缓存列表。
     */
    private final List<UserInfoModel> cacheData;
    /**
     * 用于过滤用户信息的条件函数。
     */
    private final Predicate<UserInfoModel> predicate;
    /**
     * 用于处理批量用户信息的消费函数。
     */
    private final Consumer<List<UserInfoModel>> consumer;
    
    /**
     * 存储读取过程中发生的错误信息。
     */
    // @Getter
    // private ReadErrorModel readErrorModel;
    @Setter
    private Consumer<ReadErrorModel> errorModelConsumer;
    /**
     * 构造函数，使用默认的批次大小。
     *
     * @param predicate 过滤条件，用于筛选用户信息。
     * @param consumer  处理用户信息的消费者。
     */
    public UserInfoReadListener(Predicate<UserInfoModel> predicate, Consumer<List<UserInfoModel>> consumer) {
        this(ExcelConstant.BATCH_SIZE, predicate, consumer);
    }
    
    /**
     * 构造函数，指定批次大小。
     *
     * @param batchSize    每次批量处理的用户信息数量。
     * @param predicate    过滤条件，用于确定哪些用户信息应被处理。
     * @param consumer     处理批量用户信息的函数。
     */
    public UserInfoReadListener(int batchSize, Predicate<UserInfoModel> predicate, Consumer<List<UserInfoModel>> consumer) {
        this.batchSize = batchSize;
        this.predicate = predicate;
        this.consumer = consumer;
        this.cacheData = new ArrayList<>(this.batchSize);
    }
    
    /**
     * 当读取到每个用户信息时被调用。
     * 如果当前用户信息满足过滤条件，则将其添加到缓存中。
     * 当缓存达到批量大小时，处理缓存中的用户信息并清空缓存。
     *
     * @param data    当前读取到的用户信息。
     * @param context 读取上下文，用于存储和共享在读取过程中使用的信息。
     */
    @Override
    public void invoke(UserInfoModel data, AnalysisContext context) {
        if (!this.predicate.test(data)) {
            return;
        }
        this.cacheData.add(data);
        if (this.cacheData.size() >= this.batchSize) {
            this.consumer.accept(this.cacheData);
            this.cacheData.clear();
        }
    }
    
    /**
     * 异常处理
     * 在处理过程中捕获异常时被调用。
     * 如果异常是ExcelDataConvertException，则构建错误模型并抛出ExcelAnalysisStopException停止处理。
     * 否则，直接抛出异常。
     *
     * @param exception 异常对象。
     * @param context   读取上下文。
     * @throws Exception 如果异常不是ExcelDataConvertException，则重新抛出异常。
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            int rowIndex = excelDataConvertException.getRowIndex(); // 行索引。
            int columnIndex = excelDataConvertException.getColumnIndex(); // 列索引。
            String cellData = excelDataConvertException.getCellData().getStringValue(); // 单元格数据。
            String reason = exception.getMessage(); // 异常原因。
            // this.readErrorModel = new ReadErrorModel(rowIndex, columnIndex, cellData, reason);
            if (this.errorModelConsumer != null) {
                ReadErrorModel readErrorModel = new ReadErrorModel(rowIndex, columnIndex, cellData, reason);
                this.errorModelConsumer.accept(readErrorModel);
            }
            throw new ExcelAnalysisStopException();
        }
        throw exception;
    }
    
    /**
     * 在所有用户信息读取和处理完成后被调用。
     * 如果缓存中还有剩余的用户信息，则处理这些信息。
     *
     * @param context 读取上下文。
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        if (this.cacheData.size() > 0) {
            this.consumer.accept(this.cacheData);
        }
    }
    
}
